/*******************************************************\
|* Author: Trevor Sundberg
\*******************************************************/

// Includes
#include "SkugoPlugin.hpp"
#include "SkugoFramework.hpp"
#include "SkugoConnection.hpp"
#include "SkugoPacket.hpp"
#include "SkugoBuffer.hpp"

namespace Skugo
{
  //***************************************************************************
  Guid Plugin::GuidCounter = 0;

  //***************************************************************************
  Plugin::Plugin(const string& remoteTypeName, Client* client) :
    InstanceGuid(++GuidCounter)
  {
    // Store the remote type name
    this->RemoteTypeName = remoteTypeName;

    // Store the plugins
    this->Plugins = client->LocalPlugins;

    // Store the writer
    this->Writer = client->Connection;

    // Add itself to the list of plugins
    this->Plugins->AddPlugin(this);
  }
  
  //***************************************************************************
  Plugin::~Plugin()
  {
    // Destroy the remote type
    this->DestroyRemote();

    // Remove itself from the plugins
    this->Plugins->RemovePlugin(this);
  }
  
  //***************************************************************************
  void Plugin::Send(const char* data, sizeType size)
  {
    // Send the data over the link
    this->Send(data, size, PacketType::Message);
  }

  //***************************************************************************
  void Plugin::Send(const Buffer& buffer)
  {
    this->Send(buffer.GetData(), buffer.GetSize());
  }
  
  //***************************************************************************
  void Plugin::Send(const char* data, sizeType size, PacketType::Enum type)
  {
    // Write out the packet header
    Buffer outgoing;
    outgoing.Write<unsigned>(size);
    outgoing.Write<unsigned char>((unsigned char)type);
    outgoing.Write<Guid>(this->InstanceGuid);

    // Write the provided data
    outgoing.Write(data, size);

    // Write the rest of the packet
    this->Writer->Write(outgoing.GetData(), outgoing.GetSize());
  }

  //***************************************************************************
  void Plugin::Send(const Buffer& buffer, PacketType::Enum type)
  {
    this->Send(buffer.GetData(), buffer.GetSize(), type);
  }
  
  //***************************************************************************
  Buffer Plugin::BeginCreateRemotePlugin()
  {
    // Create a binary writer to write the data
    Buffer outgoing;

    // Write the remote plugin name that we want to create (reflection name)
    outgoing.WriteAsciiString(this->RemoteTypeName);
    
    // Return the stream so that the user can add data to it
    return outgoing;
  }

  //***************************************************************************
  void Plugin::EndCreateRemotePlugin(Buffer& outgoing)
  {
    // Send the data that will create the remote plugin
    this->Send(outgoing.GetData(), outgoing.GetSize(), PacketType::Create);
  }

  //***************************************************************************
  void Plugin::CreateRemotePlugin()
  {
    auto outgoing = this->BeginCreateRemotePlugin();
    this->EndCreateRemotePlugin(outgoing);
  }
  
  //***************************************************************************
  void Plugin::OnDrop(const char* /*data*/, sizeType /*size*/, Plugin* /*source*/, void* /*sourceData*/)
  {
    // The default behavior is to do nothing, we'll let people handle it on their own
  }
  
  //***************************************************************************
  void Plugin::Receive(const char* data, sizeType size, PacketType::Enum type)
  {
    // If the packet type is just a message, then it is intended for the user
    if (type == PacketType::Message)
    {
        this->OnReceive(data, size);
    }
    else if (type == PacketType::DragDrop)
    {
      // Create a binary reader to parse the data
      // We do a const cast here because the buffer technically
      // can write (but we will never write to it)
      Buffer incoming(const_cast<char*>(data), size);

      // Figure out who the data came from
      auto sourceGuid = incoming.Read<Guid>();

      // Grab the source plugin from the container by guid
      auto sourcePlugin = this->Plugins->GetPlugin(sourceGuid);

      // Read the size of the drag drop data
      auto dragDropDataSize = incoming.Read<int>();

      // Read the rest of the data into a single byte buffer
      // This is the drag drop data that was serialized by the remote source plugin
      auto dragDropRawData = alloca(dragDropDataSize);
      incoming.Read(dragDropRawData, dragDropDataSize);

      SkugoError("Drag drop not yet handled");

      //// Create the object that represents the remote data
      //auto dragDropObjectData = sourcePlugin->DeserializeDragDropData(dragDropRawData);

      //// Make sure we were able to get the data from the source
      //if (dragDropObjectData != null)
      //{
      //  // Finally, read the rest of the data as context data from the drop plugin
      //  var contextData = incoming.ReadBytes(data.Length - (Int32)incoming.BaseStream.Position);

      //  // If anyone is listening to the event...
      //  this.OnDrop(contextData, sourcePlugin, dragDropObjectData);
      //}
    }
    else
    {
      // Otherwise, we had no idea what it was
      SkugoError("Unknown packet type encountered");
    }
  }
  
  //***************************************************************************
  void Plugin::DestroyRemote()
  {
    // Write out the destroy message
    this->Send(nullptr, 0, PacketType::Destroy);
  }
  
  //***************************************************************************
  void PluginContainer::AddPlugin(Plugin* plugin)
  {
    // Add it to the map
    this->Plugins[plugin->InstanceGuid] = plugin;
  }
    
  //***************************************************************************
  void PluginContainer::RemovePlugin(Plugin* plugin)
  {
    // Remove it from the map
    this->Plugins.erase(plugin->InstanceGuid);
  }
    
  //***************************************************************************
  Plugin* PluginContainer::GetPlugin(Guid pluginGuid)
  {
    // Get it from the map
    return this->Plugins[pluginGuid];
  }
  
  //***************************************************************************
  void PluginContainer::DestroyAllPlugins()
  {
    // Make a copy of the plugins list so we can remove as we go
    auto copy = this->Plugins;

    // Loop through all plugins and dispose each one (which removes it)
    for (auto i = copy.begin(); i != copy.end(); ++i)
    {
      delete i->second;
    }
  }
}
